package org.apache.oozie.tools.workflowgenerator.client.property;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.oozie.tools.workflowgenerator.client.OozieDiagramController;
import org.apache.oozie.tools.workflowgenerator.client.widget.NodeWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.EmailActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.FSActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.JavaActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.MapReduceActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.PigActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.PipesActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.SSHActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.ShellActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.StreamingActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.action.SubWFActionWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.control.DecisionNodeWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.control.EndNodeWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.control.ForkNodeWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.control.JoinNodeWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.control.KillNodeWidget;
import org.apache.oozie.tools.workflowgenerator.client.widget.control.StartNodeWidget;
import com.google.gwt.cell.client.ButtonCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.SelectionCell;
import com.google.gwt.cell.client.TextInputCell;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Text;

/**
 * Base abstract class for property table;
 *
 * @author egashira
 */
public abstract class PropertyTable extends ScrollPanel {

    protected NodeWidget current;
    protected TextBox name;
    protected List<NodeWidget> widgetList;
    protected List<NodeWidget> widgetDropDown;
    protected ListBox okVal;
    protected ListBox errorVal;
    protected HandlerRegistration handler;

    public PropertyTable(NodeWidget w) {
        super();
        this.current = w;
    }

    /**
     * Update a current list of created node widgets in workflow design panel
     */
    public void updateWidgetList() {

        if (widgetList == null)
            widgetList = new ArrayList<NodeWidget>();
        else
            widgetList.clear();

        Set<NodeWidget> set = current.getGenerator().getWidgets();
        for (NodeWidget w : set) {
            if (w != current)
                widgetList.add(w);
        }
    }

    /**
     * Update a list of node widgets shown in "OK" drop-down, excluding start
     * and kill nodes
     */
    public void updateWidgetDropDown() {

        // initialize widgetDropDown data for listbox, excluding Kill/Start Node
        if (widgetDropDown == null)
            widgetDropDown = new ArrayList<NodeWidget>();
        else
            widgetDropDown.clear();

        if (widgetList != null)
            for (NodeWidget w : widgetList) {
                if (!(w instanceof KillNodeWidget) && !(w instanceof StartNodeWidget)) {
                    widgetDropDown.add(w);
                }
            }

        if (okVal != null) {
            okVal.clear();
            createWidgetDropDownList(okVal);
        }
    }

    /**
     * Create a drop-down listbox of node widgets
     *
     * @param b ListBox
     */
    protected void createWidgetDropDownList(final ListBox b) {
        OozieDiagramController controller = current.getController();
        List<NodeWidget> neigh = controller.getCurrentNeighbor(current);
        b.addItem("");
        for (int i = 0; i < widgetDropDown.size(); i++) {
            NodeWidget w = widgetDropDown.get(i);
            b.addItem(prettyItemString(w));
            if (neigh != null && neigh.size() > 0 && w == neigh.get(0))
                b.setSelectedIndex(i + 1);
        }

        // remove previous handler, otherwise, end up having multiple handlers
        if (handler != null)
            handler.removeHandler();

        handler = b.addChangeHandler(new ChangeHandler() {
            @Override
            public void onChange(ChangeEvent event) {
                int selectedIndex = b.getSelectedIndex();
                if (selectedIndex > 0) {
                    NodeWidget target = widgetDropDown.get(selectedIndex - 1);
                    OozieDiagramController controller = current.getController();
                    controller.addConnection(current, target);
                }

            }
        });
    }

    /**
     * Update "Error" drop down list, currently only includes default kill node
     */
    public void updateErrorDropDownList() {
        if (errorVal == null)
            errorVal = new ListBox();
        else
            errorVal.clear();

        for (NodeWidget w : widgetList) {
            if (w instanceof KillNodeWidget) {
                errorVal.addItem(prettyItemString(w));
                errorVal.setSelectedIndex(0);
            }
        }
    }

    /**
     * Return an instance of a kill node
     *
     * @return
     */
    protected NodeWidget getKillNode() {
        NodeWidget node = null;
        for (NodeWidget w : widgetList) {
            if (w instanceof KillNodeWidget)
                node = w;
        }
        return node;
    }

    /**
     * Abstract method to generate xml elements and attach them to xml doc
     *
     * @param doc xml document
     * @param root xml element under which generated xml elements are added
     * @param next next node widget to be executed after this in workflow
     */
    public abstract void generateXML(Document doc, Element root, NodeWidget next);

    /**
     * Append a type of node widget to its name for readability
     *
     * @param w
     * @return
     */
    protected String prettyItemString(NodeWidget w) {
        StringBuilder s = new StringBuilder();
        s.append(w.getName());
        s.append(" ");
        if (w instanceof EndNodeWidget)
            s.append(" (End Node)");
        else if (w instanceof ForkNodeWidget)
            s.append(" (Fork Node)");
        else if (w instanceof JoinNodeWidget)
            s.append(" (Join Node)");
        else if (w instanceof MapReduceActionWidget)
            s.append(" (MR Action)");
        else if (w instanceof PigActionWidget)
            s.append(" (Pig Action)");
        else if (w instanceof StreamingActionWidget)
            s.append(" (MR Streaming Action)");
        else if (w instanceof PipesActionWidget)
            s.append(" (MR Pipes Action)");
        else if (w instanceof JavaActionWidget)
            s.append(" (Java Action)");
        else if (w instanceof FSActionWidget)
            s.append(" (FS Action)");
        else if (w instanceof DecisionNodeWidget)
            s.append(" (Decision Node)");
        else if (w instanceof KillNodeWidget)
            s.append(" (Kill Node)");
        else if (w instanceof SSHActionWidget)
            s.append(" (SSH Node)");
        else if (w instanceof SubWFActionWidget)
            s.append(" (SubWF Node)");
        else if (w instanceof EmailActionWidget)
            s.append(" (Email Node)");
        else if (w instanceof ShellActionWidget)
            s.append(" (Shell Node)");
        return s.toString();
    }

    /**
     * Set the width of widget
     *
     * @param w widget
     * @return
     */
    protected Widget formatCell(Widget w) {
        w.setWidth("300px");
        return w;
    }

    /**
     * Create an expandable sub table as a part of property table
     *
     * @param colname1 1st column name
     * @param colname2 2nd column name
     * @param data data list
     * @param options listbox options, if null, text input cell used
     * @return
     */
    protected CellTable<Property> createSubTable(String colname1, String colname2, List<Property> data,
            List<String> options) {

        final CellTable<Property> table = new CellTable<Property>();
        final ListDataProvider<Property> dataProvider = new ListDataProvider<Property>();
        dataProvider.setList(data);
        dataProvider.addDataDisplay(table);

        // add Name column
        Column<Property, String> nameCol = null;

        if (options == null) {
            nameCol = new Column<Property, String>(new TextInputCell()) {
                @Override
                public String getValue(Property object) {
                    return object.getName();
                }
            };
        }
        else {
            nameCol = new Column<Property, String>(new SelectionCell(options)) {
                @Override
                public String getValue(Property object) {
                    return object.getName();
                }
            };
        }

        // set event for updating value
        nameCol.setFieldUpdater(new FieldUpdater<Property, String>() {
            @Override
            public void update(int index, Property object, String value) {
                object.setName(value);
            }
        });
        table.addColumn(nameCol, colname1);

        // Add Value column
        Column<Property, String> valueCol = new Column<Property, String>(new TextInputCell()) {
            @Override
            public String getValue(Property object) {
                return object.getValue();
            }
        };

        valueCol.setFieldUpdater(new FieldUpdater<Property, String>() {
            @Override
            public void update(int index, Property object, String value) {
                object.setValue(value);
            }
        });
        table.addColumn(valueCol, colname2);

        // Button to add row
        Column<Property, String> addCol = new Column<Property, String>(new ButtonCell()) {
            @Override
            public String getValue(Property object) {
                return " + ";
            }
        };
        addCol.setFieldUpdater(new FieldUpdater<Property, String>() {
            @Override
            public void update(int index, Property object, String value) {
                dataProvider.getList().add(index + 1, new Property("", ""));
            }
        });

        table.addColumn(addCol, "");

        // Button to delete row
        Column<Property, String> delCol = new Column<Property, String>(new ButtonCell()) {
            @Override
            public String getValue(Property object) {
                return " - ";
            }
        };

        delCol.setFieldUpdater(new FieldUpdater<Property, String>() {

            @Override
            public void update(int index, Property object, String value) {
                List<Property> li = dataProvider.getList();
                if (li.size() == 1) {
                    Property p = li.get(0);
                    p.setName("");
                    p.setValue("");
                    table.redraw();
                }
                else
                    dataProvider.getList().remove(index);
            }
        });

        table.addColumn(delCol, "");

        return table;
    }

    /**
     * Create an add button in a table
     *
     * @param table
     * @return
     */
    protected Button createAddButton(Grid table) {
        Button btn = new Button("+");
        btn.getElement()
                .setAttribute("style",
                        "font-size:20px;margin:0px;padding:0px;-webkit-border-radius:10px;-moz-border-radius:10px;-border-radius:10px;");
        return btn;
    }

    /**
     * create a delete button in a table
     *
     * @param table
     * @return
     */
    protected Button createDelButton(Grid table) {
        Button btn = new Button("-");
        btn.getElement()
                .setAttribute("style",
                        "font-size:20px;margin:0px;padding:0px;-webkit-border-radius:10px;-moz-border-radius:10px;-border-radius:10px;");
        return btn;
    }

    /**
     * Create a label with common format
     *
     * @param name
     * @return
     */
    protected Label createLabel(String name) {
        Label label = new Label(name);
        label.setWidth("100px");
        label.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        return label;
    }

    /**
     * Generate xml elements of configuration
     *
     * @param list List of properties
     * @param root xml element under which configuration tag is added
     * @param doc xml document
     */
    protected void configToXML(List<Property> list, Element root, Document doc) {

        Element confele = null;

        for (Property prop : list) {
            if (prop.getName() != null && !prop.getName().matches("\\s*") && prop.getValue() != null
                    && !prop.getValue().matches("\\s*")) {

                if (confele == null) {
                    confele = doc.createElement("configuration");
                    root.appendChild(confele);
                }

                // create <property>
                Element propele = doc.createElement("property");
                confele.appendChild(propele);

                // create <name>
                Element nameele = doc.createElement("name");
                propele.appendChild(nameele);
                nameele.appendChild(doc.createTextNode(prop.getName()));

                // create <value>
                Element valele = doc.createElement("value");
                propele.appendChild(valele);
                valele.appendChild(doc.createTextNode(prop.getValue()));
            }
        }

    }

    /**
     * Generate xml elements of prepare tag
     *
     * @param list list of properties
     * @param root xml element under which prepare tag is added
     * @param doc xml document
     */
    protected void prepareToXML(List<Property> list, Element root, Document doc) {

        Element prepareele = null;
        for (Property prop : list) {
            if (prop.getName() != null && !prop.getName().matches("\\s*") && prop.getValue() != null
                    && !prop.getValue().matches("\\s*")) {

                if (prepareele == null) {
                    prepareele = doc.createElement("prepare");
                    root.appendChild(prepareele);
                }

                // create <delete> or <mkdir>
                Element ele = null;
                if (prop.getName().equals("delete"))
                    ele = doc.createElement("delete");
                else if (prop.getName().equals("mkdir"))
                    ele = doc.createElement("mkdir");

                ele.setAttribute("path", prop.getValue());
                prepareele.appendChild(ele);
            }
        }
    }

    /**
     * Generate xml elements of specified tag name
     *
     * @param list list of properties
     * @param root xml element under which new elements are added
     * @param doc xml document
     * @param key tag name
     */
    protected void filterListToXML(List<Property> list, Element root, Document doc, String key) {

        for (Property prop : list) {
            if (prop.getName() != null && !prop.getName().matches("\\s*") && prop.getValue() != null
                    && !prop.getValue().matches("\\s*")) {
                if (prop.getName().equals(key)) {
                    // create key element
                    Element nameele = doc.createElement(key);
                    root.appendChild(nameele);

                    // create text node under created element
                    Text valele = doc.createTextNode(prop.getValue());
                    nameele.appendChild(valele);
                }
            }
        }
    }

    /**
     * Return a name of a node widget
     *
     * @return
     */
    public String getName() {
        String n = null;
        if (name != null)
            n = name.getText();
        else
            n = new String("");
        return n;
    }

    /**
     * Set a name of node widget
     *
     * @param n
     */
    public void setName(String n) {
        if (name != null)
            name.setText(n);
    }

}
